#define _CRT_SECURE_NO_WARNINGS
#include <DataCore/sim_data.h>
#include <vector>
#include <sstream>
#include <fstream>
#include <Utilities/inih.h>

namespace PhysLeo {

template<typename T>
SimData<T>::SimData():
    homogeneous_(true),
    velocity_damping_param_(0.0f),
    gravity_acceleration_(glm::tvec3<T>(0.0f, 0.0f, 0.0f)),
    ele_type_(EleType::NONE), 
    fem_material_type_(FemMaterialType::NEOHOOKEAN), 
    quadrature_points_(1),
    horizon_radius_(0.0),
    pdm_material_type_(PdmMaterialType::LINEAR),
    pdm_invertible_(true),
    pdm_weight_type_(PdmWeightType::AVERAGE)
{
}

template<typename T>
void SimData<T>::readGeometry(Smesh & smesh)
{
    //rest position
    auto vertices = smesh.vertices();
    auto vert_num = smesh.vertexNum();
    ptr_rest_pos_ = std::make_shared<BufferData<glm::tvec3<T>>>(vert_num);
    ptr_rest_pos_->allocateCpu();
    auto rest_pos_ptr = ptr_rest_pos_->ptrCpu();
    for(auto i=0;i<vert_num;++i)
    {
        rest_pos_ptr[i][0] = vertices[i][0];
        rest_pos_ptr[i][1] = vertices[i][1];
        rest_pos_ptr[i][2] = vertices[i][2];
    }

    //position
    ptr_pos_ = std::make_shared<BufferData<glm::tvec3<T>>>(vert_num);
    ptr_pos_->allocateCpu();
    auto pos_ptr = ptr_pos_->ptrCpu();
    memcpy(pos_ptr, rest_pos_ptr, vert_num * sizeof(glm::tvec3<T>));

    //velocity
    ptr_vel_ = std::make_shared<BufferData<glm::tvec3<T>>>(vert_num);
    ptr_vel_->allocateCpu();
    ptr_vel_->setZeroCpu();

    //force
    ptr_force_ = std::make_shared<BufferData<glm::tvec3<T>>>(vert_num);
    ptr_force_->allocateCpu();
    ptr_force_->setZeroCpu();

    //element
    auto elements = smesh.elements();
    auto ele_num = smesh.eleNum();
    ptr_ele_ = std::make_shared<BufferData<int>>(ele_num);
    ptr_ele_->allocateCpu();
    memcpy(ptr_ele_->ptrCpu(), elements.data(), ele_num * sizeof(int));

    //element type
    ele_type_ = smesh.eleType();

    //element volume and vertices volume
    auto ele_volume = smesh.eleVolume();
    auto vert_volume = smesh.vertVolume();

    ptr_ele_volume_ = std::make_shared<BufferData<T>>(ele_num);
    ptr_ele_volume_->allocateCpu();
    auto ele_volume_ptr = ptr_ele_volume_->ptrCpu();
    for(auto i=0;i<ele_num;++i)
    {
        ele_volume_ptr[i] = ele_volume[i];
    }

    ptr_vert_volume_ = std::make_shared<BufferData<T>>(vert_num);
    ptr_vert_volume_->allocateCpu();
    auto vert_volume_ptr = ptr_vert_volume_->ptrCpu();
    for(auto i=0;i<vert_num;++i)
    {
        vert_volume_ptr[i] = vert_volume[i];
    }
}

template<typename T>
void SimData<T>::readSimulationConfig(const std::string & filename)
{

    const auto index = filename.find_last_of('/');
    const auto parent_path = (index == std::string::npos ? string(".") : filename.substr(0, index));

    INIReader reader(filename);
    if(reader.ParseError()<0)
    {
        std::cout << "fail to parse config file: " << filename << std::endl;
        exit(1);
    }
    std::cout << "reading configuration file: " << filename << std::endl;

    ptr_mass_ = std::make_shared<BufferData<T>>(ptr_pos_->size());
    ptr_mass_->allocateCpu();

    auto element_material_file = reader.Get("material", "element_material_file", "NONE");
    if (element_material_file != "NONE") 
    {
        element_material_file = parent_path + "/" + element_material_file;
        readElementMaterial(element_material_file);
    }

    auto vertex_material_file = reader.Get("material", "vertex_material_file", "NONE");
    if (vertex_material_file != "NONE")
    {
        vertex_material_file = parent_path + "/" + vertex_material_file;
        readVertexMaterial(vertex_material_file);
    }

    velocity_damping_param_ = static_cast<T>(reader.GetReal("damping", "velocity_damping", 0.0));

    const auto gravity_str = reader.Get("extforce", "gravity", "0.0 -9.8 0.0");
    std::stringstream sstream(gravity_str);
    sstream >> gravity_acceleration_[0] >> gravity_acceleration_[1] >> gravity_acceleration_[2];

    auto fixed_file = reader.Get("fixed", "fixed_file", "NONE");
    if (fixed_file != "NONE")
    {
        fixed_file = parent_path + "/" + fixed_file;
        readFixedFreedom(fixed_file);
    }

    quadrature_points_ = reader.GetInteger("fem", "quadrature_points", 1);

    horizon_radius_ = static_cast<T>(reader.GetReal("pdm", "horizon_radius", 0.0));

    pdm_invertible_ = reader.GetBoolean("pdm", "pdm_invertible", false);

    const auto pdm_weight_type_str = reader.Get("pdm", "pdm_weight_type", "AVERAGE");
    if (pdm_weight_type_str == "AVERAGE")pdm_weight_type_ = PdmWeightType::AVERAGE;
    else if (pdm_weight_type_str == "CENTER")pdm_weight_type_ = PdmWeightType::CENTER;
    else if (pdm_weight_type_str == "OUTER")pdm_weight_type_ = PdmWeightType::OUTER;
    else
    {
        std::cout << "can't identitfy the pdm weight type: " << pdm_weight_type_str << std::endl;
        exit(1);
    }


}

template<typename T>
void SimData<T>::readElementMaterial(const std::string & filename)
{
    const auto ele_vert_num = eleVertNumFromEleType(ele_type_);
    const auto ele_num = ptr_ele_->size() / ele_vert_num;

    std::fstream filein(filename);
    if(!filein.is_open())
    {
        std::cout << "can't open element material file: " << filename << std::endl;
        exit(1);
    }
    std::cout << "reading element material file: " << filename << std::endl;

    std::string fem_material_str;
    filein >> fem_material_str;
    if (fem_material_str == "LINEAR")fem_material_type_ = FemMaterialType::LINEAR;
    else if (fem_material_str == "STVK")fem_material_type_ = FemMaterialType::STVK;
    else if (fem_material_str == "COROTATED")fem_material_type_ = FemMaterialType::COROTATED;
    else if (fem_material_str == "NEOHOOKEAN")fem_material_type_ = FemMaterialType::NEOHOOKEAN;
    else
    {
        std::cout << "can't identitfy thie fem material: " << fem_material_str << std::endl;
        exit(1);
    }
    auto num_material_params = FemMaterial<T>::numMaterialParams(fem_material_type_);

    int num_materials;
    filein >> num_materials;
    homogeneous_ = (num_materials == 1) ;

    if (homogeneous_)ptr_ele_material_ = std::make_shared<BufferData<T>>(num_material_params);
    else ptr_ele_material_ = std::make_shared<BufferData<T>>(num_material_params*ele_num);
    ptr_ele_material_->allocateCpu();

    T density;
    std::vector<T> material_param(num_material_params);
    filein >> density;
    for (auto i = 0; i < num_material_params; ++i)filein >> material_param[i];
    //convert material_param[0] material_param[1]  from young's module and poisson ratio to the first and second lame coefficients
    T Ey = material_param[0], poisson = material_param[1];
    material_param[0] = Ey * poisson / (1.0f + poisson) / (1.0f - 2.0f * poisson);
    material_param[1] = Ey / 2.0f / (1.0f + poisson);
    //assign to all material and mass
    auto ele_material_ptr = ptr_ele_material_->ptrCpu();
    for(auto i=0;i<(homogeneous_?1:ele_num);++i)
    {
        memcpy(ele_material_ptr + i * num_material_params, material_param.data(), num_material_params * sizeof(T));
    }

    auto mass_ptr = ptr_mass_->ptrCpu();
    auto vert_volume_ptr = ptr_vert_volume_->ptrCpu();
    for(auto i=0;i<ptr_mass_->size();++i)
    {
        mass_ptr[i] = density * vert_volume_ptr[i];
    }

    if(!homogeneous_)
    {
        std::cout << "not implemented yet" << std::endl;
    }
    filein.close();
}

template<typename T>
void SimData<T>::readVertexMaterial(const std::string & filename)
{
    auto vert_num = ptr_pos_->size();
    std::fstream filein(filename);
    if (!filein.is_open())
    {
        std::cout << "can't open vertices material file: " << filename << std::endl;
        exit(1);
    }
    std::cout << "reading vertex material file: " << filename << std::endl;
    string pdm_material_str;
    filein >> pdm_material_str;
    if (pdm_material_str == "LINEAR")pdm_material_type_ = PdmMaterialType::LINEAR;
    else if (pdm_material_str == "NONLINEAR")pdm_material_type_ = PdmMaterialType::NONLINEAR;
    else if (pdm_material_str == "ANISO")pdm_material_type_ = PdmMaterialType::ANISO;
    else if (pdm_material_str == "ANISO_NONLINEAR")pdm_material_type_ = PdmMaterialType::ANISO_NONLINEAR;
    else if (pdm_material_str == "STVK")pdm_material_type_ = PdmMaterialType::STVK;
    else if (pdm_material_str == "NEOHOOKEAN")pdm_material_type_ = PdmMaterialType::NEOHOOKEAN;
    else
    {
        std::cout << "can't identitfy thie pdm material: " << pdm_material_str << std::endl;
        exit(1);
    }
    auto num_material_params = PdmMaterial<T>::numMaterialParams(pdm_material_type_);

    int num_materials;
    filein >> num_materials;
    homogeneous_ = (num_materials == 1);

    if (homogeneous_)ptr_vert_material_ = std::make_shared<BufferData<T>>(num_material_params);
    else ptr_vert_material_ = std::make_shared<BufferData<T>>(num_material_params*vert_num);
    ptr_vert_material_->allocateCpu();

    T density;
    std::vector<T> material_param(num_material_params);
    filein >> density;
    for (auto i = 0; i < num_material_params; ++i)filein >> material_param[i];
    //convert material_param[0] material_param[1]  from young's module and poisson ratio to the first and second lame coefficients
    T Ey = material_param[0], poisson = material_param[1];
    material_param[0] = Ey * poisson / (1.0f + poisson) / (1.0f - 2.0f * poisson);
    material_param[1] = Ey / 2.0f / (1.0f + poisson);
    //assign to all material and mass
    auto vert_material_ptr = ptr_vert_material_->ptrCpu();
    for (auto i = 0; i<(homogeneous_ ? 1 : vert_num); ++i)
    {
        memcpy(vert_material_ptr + i * num_material_params, material_param.data(), num_material_params * sizeof(T));
    }

    auto mass_ptr = ptr_mass_->ptrCpu();
    auto vert_volume_ptr = ptr_vert_volume_->ptrCpu();
    for (auto i = 0; i<ptr_mass_->size(); ++i)
    {
        mass_ptr[i] = density * vert_volume_ptr[i];
    }
    ptr_mass_->cpu2Gpu();

    if (!homogeneous_)
    {
        std::cout << "not implemented yet" << std::endl;
    }
    filein.close();
}

template<typename T>
void SimData<T>::readFixedFreedom(const std::string & filename)
{
    std::fstream filein(filename.c_str());
    if(!filein.is_open())
    {
        std::cout << "can't open fixed freedom file: " << filename << std::endl;
        exit(1);
    }
    std::cout << "reading fixed freedom file: " << filename << std::endl;

    std::vector<int> fixed_vector;

    std::string fixed_str;

    bool x, y, z;
    filein >> x >> y >> z;
    int index;
    while (filein >> index)
    {
        if (x)fixed_vector.push_back(3 * (index - 1));
        if (y)fixed_vector.push_back(3 * (index - 1) + 1);
        if (z)fixed_vector.push_back(3 * (index - 1) + 2);
    }
    filein.clear(); //clear the fail bit, begin next fixed input

    auto size = static_cast<int>(fixed_vector.size());
    ptr_fix_ = std::make_shared<BufferData<int>>(size);
    ptr_fix_->allocateCpu();
    memcpy(ptr_fix_->ptrCpu(), fixed_vector.data(), size * sizeof(int));

    filein.close();
}

template class SimData<float>;
template class SimData<double>;

}